/// IO操作
module jinji.util.io;

import std.process;
import std.stdio;
public import std.stdio : stdin;

public import tame.buffer : StringSink;

/+
import tame.io.file,
tame.io.stdio,
wad.process;
+/
debug (Log) import std.array, tame.logger;

/// 生成一个Curl对象
Curl initCurl(in char[] url, long timeout = 8) {
	Curl c = Curl(null);
	c.url = url;
	c.set(CurlOption.followlocation, 1L);
	with (CurlProto)
		c.set(CurlOption.protocols, http | https | file);
	c.set(CurlOption.maxredirs, 10);
	c.connTimeout = timeout;
	c.timeout = timeout;
	return c;
}

version (Mock) {
	public import etc.c.curl : CurlError, CurlOption, CurlProto;

	struct Curl {
		this(Args...)(Args args) {
		}

		@property int opDispatch(string name, Args...)(Args args)
			=> 0;
	}
} else {
	import dast.net.curl;
	public import dast.net.curl : Curl, CurlOption, CurlError;

	/// 设置URL
	@property void url(Curl c, in char[] url) {
		import jinji.config,
		jinji.util.error,
		jinji.util.string;

		checkArg(url.stringof, url.length <= maxURLLength, "URL长度超过限制");

		wchar[maxURLLength] wbuf = void;
		char[maxURLLength + 1] ansi = void;
		const len = url.toANSI(wbuf, ansi);
		ansi[len] = 0;
		c.set(CurlOption.url, ansi.ptr);
	}
}

/++
运行一个外部命令
Params:
	cmd = 命令行参数
	input = 输入文件
	err = 错误输出文件
	blocking = 是否阻塞
Return:
	stdout输出文件
Throws:
	ProcessException
+/
File spawn(in char[][] cmd, File input, StringSink* err = null, bool blocking = false) {
	import core.thread,
	core.time,
	jinji.config,
	jinji.rpc,
	jinji.server;

	Pipe stdout = pipe(),
	stderr = pipe();
	const start = now;
	debug (Log)
		info("cmd: ", cmd.join(' '));
	Pid pid = spawnProcess(cmd, input, stdout.writeEnd, stderr.writeEnd);
	typeof(tryWait(pid)) p;
	const timeout = C.timeout * 10_000UL;
	if (blocking) {
		//p = waitTimeout(pid, timeout.hnsecs);
		//if (!p.terminated)
		//	kill(pid);
	} else {
		while (!(p = tryWait(pid)).terminated) {
			if (now - start > timeout) {
				kill(pid);
				throw new ProcessException(_("进程超时"));
			}
			Fiber.yield();
		}
	}
	version (App) {
		loop.workerPool.run!readTo(stderr.readEnd, err);
	}
	return stdout.readEnd;
}

/// 按分隔符读取文件
struct ByTerminator(uint N = 1024) {
	private File f;
	/// 缓冲区
	char[N] buf = void;
	/// 数据
	char[] data;
	private size_t frontLen;
	/// 分隔符
	char terminator;
	/// 是否保留分隔符
	bool keepTerminator;

	this(File file, char term, bool keepTerm = false) @safe {
		f = file;
		terminator = term;
		keepTerminator = keepTerm;
	}

	@disable this(this);

	void read() @trusted {
		import core.stdc.string : memchr, memmove;

		if (!frontLen && file.isOpen) {
			if (!data.length)
				data = cast(char[])f.rawRead(buf);
			if (data.length) {
				auto p = memchr(data.ptr, terminator, data.length);
				if (!p && data.length < buf.length) {
					memmove(buf.ptr, data.ptr, data.length);
					const len = f.rawRead(buf[data.length .. $]).length;
					data = buf[0 .. data.length + len];
					p = memchr(buf.ptr, terminator, data.length);
				}
				frontLen = p ? p - cast(void*)data.ptr + keepTerminator : data.length;
			}
		}
	}

	/// 获取文件句柄
	@property {
		auto file() pure => f;

		bool empty() {
			read();
			return data.length == 0;
		}
	}

nothrow @nogc:
	@property auto front()
		=> data[0 .. frontLen];

	void popFront() {
		if (keepTerminator) {
			data = data[frontLen .. $];
		} else {
			data = data[frontLen + (frontLen < data.length) .. $];
		}
		frontLen = 0;
	}
}

private:

/++
读取文件到StringSink
Params:
	file = 文件
	sink = 输出，若为null则丢弃
+/
void readTo(File file, StringSink* sink) {
	char[1024] buf = void;
	while (true) {
		auto data = file.rawRead(buf);
		if (data.length) {
			if (sink)
				*sink ~= data;
		} else
			break;
	}
	// BUG: 读取时卡住，CPU占用高
	//foreach (ch; LockingTextReader(file)) {
	//}
}
